using SharpDX;
using SharpDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace Engine.Graphics
{
    public struct Material
    {
        #region Private Fields

        private static int randomColor = 1;
        private string bumpTextureName1;
        private string bumpTextureName2;
        private string bumpTextureName3;
        private Color4 color;
        private uint hashCode;
        private EnumMappingMode mappingMode;
        private Mappings mappings1;
        private Mappings mappings2;
        private Mappings mappings3;

        /// <summary>
        /// Meshes and SubsetId
        /// </summary>
        private Dictionary<Meshobject, List<int>> registeredMeshes;

        private string textureName1;
        private string textureName2;
        private string textureName3;

        #endregion Private Fields

        #region Public Constructors

        public Material(Material material)
        {
            this.hashCode = 0;
            this.textureName1 = material.textureName1;
            this.textureName2 = material.textureName2;
            this.textureName3 = material.textureName3;

            this.bumpTextureName1 = material.bumpTextureName1;
            this.bumpTextureName2 = material.bumpTextureName2;
            this.bumpTextureName3 = material.bumpTextureName3;

            this.mappings1 = material.mappings1;
            this.mappings2 = material.mappings2;
            this.mappings3 = material.mappings3;
            this.mappingMode = material.mappingMode;
            this.registeredMeshes = new Dictionary<Meshobject, List<int>>();
            this.color = new Color4(1, 1, 1, 1);
            this.color = new Color4(GetRandomColor());
            UpdateHashCode();
        }

        /// <summary>
        /// Creates a Material. Texture names should be given without fileextension.
        /// </summary>
        /// <param name="TextureID1"></param>
        /// <param name="TextureID2"></param>
        /// <param name="TextureID3"></param>
        /// <param name="mappings"></param>
        public Material(string TextureName1, string TextureName2, string TextureName3, EnumMappingMode mappings)
            : this(TextureName1, TextureName2, TextureName3, string.Empty, string.Empty, string.Empty, mappings, Vector4.Zero, Vector4.Zero)
        {
        }

        public Material(int TextureID1, int TextureID2, int TextureID3, EnumMappingMode enumMappingMode, MaterialManager materialManager)
            : this(materialManager.TextureNames[TextureID1], materialManager.TextureNames[TextureID2], materialManager.TextureNames[TextureID3], enumMappingMode)
        {
        }

        public Material(string TextureName1, string TextureName2, string TextureName3, EnumMappingMode mappings, Mappings mappingTexture1, Mappings mappingTexture2, Mappings mappingTexture3)
            : this(TextureName1, TextureName2, TextureName3, mappings)
        {
            this.mappings1 = mappingTexture1;
            this.mappings2 = mappingTexture2;
            this.mappings3 = mappingTexture3;
            this.color = new Color4(GetRandomColor());

            UpdateHashCode();
        }

        public Material(int TextureID1, int TextureID2, int TextureID3, EnumMappingMode enumMappingMode, MaterialManager materialManager, Mappings mappingTexture1, Mappings mappingTexture2, Mappings mappingTexture3)
            : this(materialManager.TextureNames[TextureID1], materialManager.TextureNames[TextureID2], materialManager.TextureNames[TextureID3], enumMappingMode, mappingTexture1, mappingTexture2, mappingTexture3)
        {
        }

        public Material(string TextureName1, string TextureName2, string TextureName3, string bumpTextureName1, string bumpTextureName2, string bumpTextureName3, EnumMappingMode mappings, Vector4 texMod1, Vector4 texMod2)
        {
            this.mappingMode = mappings;
            this.textureName1 = TextureName1;
            this.textureName2 = TextureName2;
            this.textureName3 = TextureName3;

            this.bumpTextureName1 = bumpTextureName1;
            this.bumpTextureName2 = bumpTextureName2;
            this.bumpTextureName3 = bumpTextureName3;

            this.mappings1 = new Mappings(texMod1, texMod2);
            this.mappings2 = new Mappings();
            this.mappings3 = new Mappings();
            this.registeredMeshes = new Dictionary<Meshobject, List<int>>();
            this.hashCode = 0;
            this.color = new Color4(1, 1, 1, 1);
            this.color = new Color4(GetRandomColor());
            UpdateHashCode();
        }

        #endregion Public Constructors

        #region Public Properties

        public string BumpTextureName1
        {
            get
            {
                return this.bumpTextureName1;
            }
            set
            {
                this.bumpTextureName1 = value;
                UpdateHashCode();
            }
        }

        public string BumpTextureName2
        {
            get
            {
                return this.bumpTextureName2;
            }
            set
            {
                this.bumpTextureName2 = value;
                UpdateHashCode();
            }
        }

        public string BumpTextureName3
        {
            get
            {
                return this.bumpTextureName3;
            }
            set
            {
                this.bumpTextureName3 = value;
                UpdateHashCode();
            }
        }

        public EnumMappingMode MappingMode
        {
            get
            {
                return mappingMode;
            }
            set
            {
                mappingMode = value;
            }
        }

        public Mappings Mappings1
        {
            get
            {
                return this.mappings1;
            }
            set
            {
                this.mappings1 = value;
                UpdateHashCode();
            }
        }

        public Mappings Mappings2
        {
            get
            {
                return this.mappings2;
            }
            set
            {
                this.mappings2 = value;
                UpdateHashCode();
            }
        }

        public Mappings Mappings3
        {
            get
            {
                return this.mappings3;
            }
            set
            {
                this.mappings3 = value;
                UpdateHashCode();
            }
        }

        public Color4 MaterialColor
        {
            get { return color; }
            set { color = value; }
        }

        public Dictionary<Meshobject, List<int>> RegisteredMeshes
        {
            get { return registeredMeshes; }
        }

        public string TextureName1
        {
            get
            {
                return this.textureName1;
            }
            set
            {
                this.textureName1 = value;
                UpdateHashCode();
            }
        }

        public string TextureName2
        {
            get
            {
                return this.textureName2;
            }
            set
            {
                this.textureName2 = value;
                UpdateHashCode();
            }
        }

        public string TextureName3
        {
            get
            {
                return this.textureName3;
            }
            set
            {
                this.textureName3 = value;
                UpdateHashCode();
            }
        }

        #endregion Public Properties

        #region Public Methods

        public static Material FromStream(BinaryReader br)
        {
            Material material = new Material(br.ReadString(),
                                             br.ReadString(),
                                             br.ReadString(),
                                            (EnumMappingMode)br.ReadByte(),
                                             Mappings.FromStream(br),
                                             Mappings.FromStream(br),
                                             Mappings.FromStream(br)
                                             );

            //material.MappingMode = EnumMappingMode.material00;
            material.UpdateHashCode();

            return material;
        }

        public static Material FromXmlStream(XmlReader reader)
        {
            if (reader.NodeType == XmlNodeType.Element
                        && reader.Name == "Material")
            {
                Material material = new Material(reader.GetAttribute("Texture1"), reader.GetAttribute("Texture2"), reader.GetAttribute("Texture3"), (EnumMappingMode)Convert.ToInt32(reader.GetAttribute("MappingMode")));
                int currentMapping = 1;
                while (reader.Read() && ((reader.Name != "Material") && (reader.NodeType != XmlNodeType.EndElement)))
                {
                    if (reader.Name == "MaterialMapping" && currentMapping == 1)
                    {
                        material.mappings1 = Mappings.FromXmlReader(reader);
                        currentMapping++;
                        continue;
                    }
                    if (reader.Name == "MaterialMapping" && currentMapping == 2)
                    {
                        material.mappings2 = Mappings.FromXmlReader(reader);
                        currentMapping++;
                        continue;
                    }
                    if (reader.Name == "MaterialMapping" && currentMapping == 3)
                    {
                        material.mappings3 = Mappings.FromXmlReader(reader);
                        currentMapping++;
                        continue;
                    }
                }
                return material;
            }
            throw new Exception("No Material found at the position in xml-file: " + reader.Name);
        }

        public static bool operator !=(Material material1, Material material2)
        {
            if (material1.hashCode != material2.hashCode) return true; return false;
        }

        public static bool operator ==(Material material1, Material material2)
        {
            if (material1.hashCode == material2.hashCode) return true; return false;
        }

        public override bool Equals(object obj)
        {
            return (obj is Material) && (this == (Material)obj);
        }

        public override int GetHashCode()
        {
            return (int)this.hashCode;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="mesh">the mesh which uses this Material</param>
        /// <param name="subsetId">the subset which uses this Material</param>
        public void RegisterMesh(Meshobject meshId, int subsetId)
        {
            List<int> subsets;
            lock (this.registeredMeshes)
                if (this.registeredMeshes.ContainsKey(meshId))
                {
                    var item = this.registeredMeshes.TryGetValue(meshId, out subsets);
                    subsets.Add(subsetId);
                }
                else
                {
                    subsets = new List<int>();
                    subsets.Add(subsetId);
                    lock (this.registeredMeshes)
                        this.registeredMeshes.Add(meshId, subsets);
                }
        }

        public void SaveToStream(BinaryWriter bw)
        {
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName1));
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName2));
            bw.Write(Path.GetFileNameWithoutExtension(this.textureName3));
            bw.Write((byte)this.mappingMode);
            this.mappings1.SaveToStream(bw);
            this.mappings2.SaveToStream(bw);
            this.mappings3.SaveToStream(bw);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("T1: " + this.textureName1);
            sb.Append(" T2: " + this.textureName2);
            sb.Append(" T3: " + this.textureName3);
            //sb.Append("  Tex1: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture1ID]));
            //sb.Append("  Tex2: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture2ID]));
            //sb.Append("  Tex3: " + Path.GetFileNameWithoutExtension(MaterialManager.TexturePaths[wr2Surface.texture3ID]));
            sb.Append(" " + ((EnumMappingMode)mappingMode).ToString());
            return sb.ToString();
        }

        public void ToXmlStream(XmlWriter writer)
        {
            writer.WriteComment("Material for Scenery Editor V1.0");

            writer.WriteStartElement("Material");

            writer.WriteAttributeString("Texture1", Path.GetFileNameWithoutExtension(this.textureName1));
            writer.WriteAttributeString("Texture2", Path.GetFileNameWithoutExtension(this.textureName2));
            writer.WriteAttributeString("Texture3", Path.GetFileNameWithoutExtension(this.textureName3));

            writer.WriteAttributeString("MappingMode", ((byte)this.mappingMode).ToString());

            this.mappings1.ToXmlStream(writer);
            this.mappings2.ToXmlStream(writer);
            this.mappings3.ToXmlStream(writer);

            writer.WriteEndElement();
        }

        #endregion Public Methods

        #region Internal Methods

        internal ShaderResourceView GetBumpTexture1(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName1);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal ShaderResourceView GetBumpTexture2(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName2);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal ShaderResourceView GetBumpTexture3(MaterialManager materialManager)
        {
            int index = materialManager.GetTextureID(this.bumpTextureName3);
            if (index > -1)
            {
                //index = 109;
                return materialManager.Textures[index];
            }
            return materialManager.DummyNormalMap;
        }

        internal Vector4 GetMappings1A()
        {
            return this.mappings1.mappings1;
        }

        internal Vector4 GetMappings1B()
        {
            return this.mappings1.mappings2;
        }

        internal Vector4 GetMappings2A()
        {
            return this.mappings2.mappings1;
        }

        internal Vector4 GetMappings2B()
        {
            return this.mappings2.mappings2;
        }

        internal Vector4 GetMappings3A()
        {
            return this.mappings3.mappings1;
        }

        internal Vector4 GetMappings3B()
        {
            return this.mappings3.mappings2;
        }

        internal ShaderResourceView GetTexture1(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName1)];
        }

        internal ShaderResourceView GetTexture2(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName2)];
        }

        internal ShaderResourceView GetTexture3(MaterialManager materialManager)
        {
            return materialManager.Textures[materialManager.GetTextureID(this.textureName3)];
        }

        internal void UnregisterMesh(Meshobject mesh)
        {
            lock (this.registeredMeshes)
                if (this.registeredMeshes.ContainsKey(mesh))
                    this.registeredMeshes.Remove(mesh);
        }

        #endregion Internal Methods

        #region Private Methods

        /// <summary>
        /// Create a Random color by random ints
        /// </summary>
        /// <returns></returns>
        private Color4 GetRandomColor()
        {
            Random random = new Random(Material.randomColor);
            Material.randomColor = random.Next();
            return new Color4((float)random.Next(256) / 255, (float)random.Next(256) / 255, (float)random.Next(256) / 255, 1.0f);
        }

        private void UpdateHashCode()
        {
            AdlerChecksum adlerChecksum = new AdlerChecksum();
            byte[] data = new byte[40];

            BitConverter.GetBytes(this.textureName1.GetHashCode()).CopyTo(data, 0);
            BitConverter.GetBytes(this.textureName2.GetHashCode()).CopyTo(data, 4);
            BitConverter.GetBytes(this.textureName3.GetHashCode()).CopyTo(data, 8);
            BitConverter.GetBytes(this.mappings1.CRC).CopyTo(data, 12);
            BitConverter.GetBytes(this.mappings2.CRC).CopyTo(data, 16);
            BitConverter.GetBytes(this.mappings3.CRC).CopyTo(data, 20);
            BitConverter.GetBytes((int)this.mappingMode).CopyTo(data, 24);
            BitConverter.GetBytes(this.bumpTextureName1.GetHashCode()).CopyTo(data, 28);
            BitConverter.GetBytes(this.bumpTextureName2.GetHashCode()).CopyTo(data, 32);
            BitConverter.GetBytes(this.bumpTextureName3.GetHashCode()).CopyTo(data, 36);
            adlerChecksum.MakeForBuff(data);

            this.hashCode = adlerChecksum.ChecksumValue;//
        }

        #endregion Private Methods
    }
}